package com.yxx.quartz.utils;

import cn.hutool.extra.spring.SpringUtil;
import com.yxx.entity.Task;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

/**
 * 任务执行工具
 * @author yuxiangxun
 * @date 2025/2/17
 * @apiNote
 */
public class TaskInvokeUtils {

    /**
     * 执行方法
     * 该方法根据传入的定时任务对象，反射调用指定的目标方法
     *
     * @param task 定时任务对象，包含调用目标的信息
     * @throws Exception 如果调用过程中发生错误，则抛出异常
     */
    public static void invokeMethod(Task task) throws Exception {
        // 获取调用目标字符串，格式为：beanName.methodName(arg1, arg2, ...)
        String invokeTarget = task.getInvokeTarget();

        // 从调用目标字符串中提取bean名称
        String beanName = getBeanName(invokeTarget);

        // 从调用目标字符串中提取方法名称
        String methodName = getMethodName(invokeTarget);

        // 从调用目标字符串中提取方法参数列表
        List<Object[]> methodParams = getMethodParams(invokeTarget);

        // 判断beanName是否一个有效的类名
        if(!isValidClassName(beanName)){
            // 如果不是有效的类名，则从Spring容器中获取bean实例
            Object bean = SpringUtil.getBean(beanName);

            // 反射调用目标方法
            invokeMethod(bean, methodName, methodParams);
        }else{
            // 如果是有效的类名，则通过类名实例化对象
            Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();

            // 反射调用目标方法
            invokeMethod(bean, methodName, methodParams);
        }
    }

    /**
     * 调用任务方法
     *
     * @param bean         目标对象
     * @param methodName   方法名称
     * @param methodParams 方法参数
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        // 当方法参数不为空时，根据参数类型和值调用方法
        if (methodParams != null && methodParams.size() > 0) {
            // 获取包含参数类型的方法
            Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
            // 调用方法并传递参数值
            method.invoke(bean, getMethodParamsValue(methodParams));
        } else {
            // 当方法参数为空时，调用无参数的方法
            Method method = bean.getClass().getDeclaredMethod(methodName);
            // 调用方法
            method.invoke(bean);
        }
    }

    /**
     * 校验是否为 class 包名
     * 该方法用于判断给定的字符串是否符合 class 包名的命名规范
     * 主要逻辑是检查字符串中是否包含超过一个点号，因为 class 包名至少包含两个点号分隔的包名和类名
     * @param invokeTarget 调用目标
     *        这是一个字符串参数，表示可能的 class 包名
     * @return true 是  false 否
     *       返回值表示给定的字符串是否为有效的 class 包名
     */
    public static boolean isValidClassName(String invokeTarget){
        // 使用 Apache Commons Lang 的 StringUtils 来计算字符串中点号的数量
        // 如果点号的数量大于 1，则认为该字符串可能是一个有效的 class 包名
        return  StringUtils.countMatches(invokeTarget, ".") > 1;
    }

    /**
     * 获取 bean 名称
     * @param invokeTarget 调用目标
     * @return bean 名称
     */
    public static String getBeanName(String invokeTarget){
        // 提取调用目标字符串中的 bean 名称，去除方法名和参数部分
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
        // 继续处理，去除最后的点号，以获取纯净的 bean 名称
        return StringUtils.substringBeforeLast(beanName,".");
    }

    /**
     * 获取bean方法名
     *
     * @param invokeTarget 调用目标字符串，格式为"beanName.methodName()"
     * @return 提取的method方法名，不包含参数部分
     */
    public static String getMethodName(String invokeTarget) {
        // 从调用目标字符串中提取方法名，去除参数部分
        String methodName = StringUtils.substringBefore(invokeTarget, "(");
        // 进一步提取，去除bean名和最后的点号，得到纯方法名
        return StringUtils.substringAfterLast(methodName, ".");
    }

    /**
     * 获取method方法参数相关列表
     *
     * @param invokeTarget 调用目标
     * @return method方法相关参数列表
     */
    public static List<Object[]> getMethodParams(String invokeTarget) {
        // 提取方法参数部分的字符串
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr)) {
            return null;
        }
        // 使用正则表达式分割方法参数，考虑参数中可能包含逗号的情况
        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
        List<Object[]> clazz = new LinkedList<>();
        for (String methodParam : methodParams) {
            String str = StringUtils.trimToEmpty(methodParam);
            // 根据参数类型，添加到列表中
            // String字符串类型，以'或"开头
            if (StringUtils.startsWithAny(str, "'", "\"")) {
                clazz.add(new Object[]{StringUtils.substring(str, 1, str.length() - 1), String.class});
            }
            // boolean布尔类型，等于true或者false
            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) {
                clazz.add(new Object[]{Boolean.valueOf(str), Boolean.class});
            }
            // long长整形，以L结尾
            else if (StringUtils.endsWith(str, "L")) {
                clazz.add(new Object[]{Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class});
            }
            // double浮点类型，以D结尾
            else if (StringUtils.endsWith(str, "D")) {
                clazz.add(new Object[]{Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class});
            }
            // 其他类型归类为整形
            else {
                clazz.add(new Object[]{Integer.valueOf(str), Integer.class});
            }
        }
        return clazz;
    }

    /**
     * 获取参数类型
     *
     * @param methodParams 参数相关列表
     * @return 参数类型列表
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
        // 初始化一个Class数组，用于存储方法参数的类型
        Class<?>[] clazz = new Class<?>[methodParams.size()];
        // 遍历参数列表
        int index = 0;
        for (Object[] os : methodParams) {
            // 将每个参数的类型（在数组中的第二个元素）转换为Class类型并存储到clazz数组中
            clazz[index] = (Class<?>) os[1];
            index++;
        }
        // 返回参数类型数组
        return clazz;
    }

    /**
     * 获取参数值
     *
     * 该方法用于从一个包含参数相关数组的列表中提取每个数组的第一个元素，并将其组合成一个新的数组返回
     * 这在反射调用方法时特别有用，当需要从一个参数列表中提取出实际的参数值传递给方法时
     *
     * @param methodParams 参数相关列表，每个元素是一个对象数组，其中第一个元素是参数值
     * @return 参数值列表，一个对象数组，包含了从参数列表中提取的所有参数值
     */
    public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
        // 创建一个与参数列表大小相同的数组，用于存储参数值
        Object[] clazz = new Object[methodParams.size()];
        // 初始化索引变量，用于在遍历时跟踪当前参数的位置
        int index = 0;
        // 遍历参数列表，提取每个参数数组的第一个元素（参数值），并将其添加到结果数组中
        for (Object[] os : methodParams) {
            // 将当前参数数组的第一个元素添加到结果数组中
            clazz[index] = os[0];
            // 更新索引变量，指向下一个参数位置
            index++;
        }
        // 返回包含所有参数值的数组
        return clazz;
    }

}
